import struct,os,hashlib,timeit,math,re,threading,time,multiprocessing,io

from google.protobuf import descriptor
from utils import ExcelUtil
from utils import FileUtil
from utils import PBGenUtil,EncryptUtil,SettingUtil
from ExportExcelWorker import ExportExcelWorker
from ExportSetting import ExportSetting

#把多个excel配置表转换成protobuffer格式的二进制文件
'''
最终保存的数据格式:
    4个字节写入文件头 head;当 usePB=true 时 head=0xff,否则 head=0xfe
    4个字节掩码 mask:0xee 
    4个字节写入 sheet 表格个数 N :
    循环 N 写入:
        2个字节(无符号)写入表名长度:
        写入表名，字符串
        4个字节写入单个sheet表格数据总长度
            4个字节的总行数(int)M
            2个字节无符号整型写入主键长度(大于0表示有主键)
                有主键则写入主键字段名字符串，多个用下划线拼接；例如:id_level
            当 usePB = false 时才写入：
                2个字节无符号整型写入字段长度
                写入用英文逗号(,)隔开的字段字符串;例如:id,name,icon
                2个字节无符号整型写入字段数据类型信息长度
                写入用英文逗号(,)隔开字段数据类型字符串;例如:int,string,int
            循环 M 写入表格数据
                4个字节写入单行数据长度
                当 usePB = true 时: 
                    写入一行PB格式的二进制数据(真实的PB格式数据)
                当 usePB = false 时: 
                    写入根据字段数据类型把数据写二进制数据

    写入文件尾 tail = head^mask 可以做简单的验证

'''

def writeExcelToPB(root:str):
    print(f"开始读取[{root}]的所有excel表格..")
    oldTableMap = FileUtil.readConfigTableMap()
    excelFiles = []
    oldTables = []
    FileUtil.listRootExcels(root,excelFiles,oldTables,oldTableMap)
    oldChangeSets = set(oldTables)
    sheetInfoArr = ExcelUtil.readSheetInfos(excelFiles)

    print("开始查询可能修改的表格并且生成数据 总配置数=",len(sheetInfoArr))
    
    changeSheetInfos,newTableSetToWrite = ExcelUtil.filterConfig(sheetInfoArr)
    newTabeSetToFind = set([item.split("=")[0] for item in newTableSetToWrite]) #修改或新增

    st = timeit.default_timer()
    ExcelUtil.writeSheetToPB(changeSheetInfos)

    changeNum = len(changeSheetInfos)

    et = timeit.default_timer()
    costMS = et-st
    print(f"可能被修改的表格总数={changeNum} costMS=",costMS)
    
    delSet = oldChangeSets - newTabeSetToFind
    addSet = newTabeSetToFind - oldChangeSets #通过差集 查找新增的配置
    for url in oldTableMap.keys():
        tabs = oldTableMap[url]
        if os.path.exists(url):
            for tab in tabs:
                newTableSetToWrite.add(tab+"="+url)
        else:
            delSet = delSet.union(set(tabs)) 

    FileUtil.writeConfigTableSet(newTableSetToWrite)
    extName = SettingUtil.getSetting().dataExt
    for pbFile in delSet:
        url = os.path.join(SettingUtil.getSetting().sheetPbRoot,pbFile+extName)
        if os.path.exists(url):
            print("**删除差集中的配置 url=",url)
            os.remove(url)
            
    print(f"**!!本次操作被删除的表!!**,delSet={delSet}")
    print(f"本次操作新增的表,addSet={addSet}")
    print(f"本次操作变化的表,{newTabeSetToFind}")
    forceWrite = False 
    pbFile = SettingUtil.getSetting().pbFile
    if changeNum > 0 or not os.path.exists(pbFile) or forceWrite:
        writeConfigPB()

    if changeNum ==0:
        print("**!!数据没有变化无需重新导表,请再次确认你的操作..")


def writeConfigPB():
    print("开始导出所有配置数据..")
    sheetPbRoot = SettingUtil.getSetting().sheetPbRoot
    extName = SettingUtil.getSetting().dataExt
    sheetPBs = [os.path.join(sheetPbRoot,pb) for pb in os.listdir(sheetPbRoot) if os.path.splitext(pb)[1]==extName]
    curCount = len(sheetPBs)
    if  curCount > 0:
        ioBytes = io.BytesIO()
        writor = io.BufferedWriter(ioBytes)
        writor.seek(0)
        usePB = SettingUtil.getSetting().usePB
        head = 0xff if usePB else 0xfe #如果是PB格式就写入 255 否则写入 254
        mask = 0xee
        writor.write(struct.pack("<i",head)) #写入头 小端
        writor.write(struct.pack("<i",mask)) #写掩码
        writor.write(struct.pack("<i",curCount)) #表格个数
        for pbFile in sheetPBs:
            with open(pbFile,"rb") as f:
                writor.write(f.read())
        tail = head ^ mask
        writor.write(struct.pack("<i",tail)) #写入尾
        writor.flush()
        totalBytes = ioBytes.getbuffer().tobytes()
        by = len(totalBytes)
        print("所有配置数据 总大小=",math.floor(by/1024*1000)/1000,"KB",by,"个字节")
        FileUtil.writePBFile(SettingUtil.getSetting().pbFile,totalBytes)
        FileUtil.writeCurMD5Map()
        writor.close()

__START_TIME__ = 0

def exportTable():
    global __START_TIME__
    st = timeit.default_timer()
    __START_TIME__ = st
    useWorker = True
    root = SettingUtil.getSetting().excelRoot
    try:
        if useWorker:
            worker = ExportExcelWorker(onWorkerEnd,False)
            worker.writeExcelToPB(root)
        else:
            writeExcelToPB(root)
    except Exception as e:
        print('!!!! 导表失败！！error=',e.with_traceback())
        return
    if not useWorker:
        et = timeit.default_timer()
        costMS = math.floor((et-st)*1000)
        print(f'~~导表完成~not worker~~耗时{costMS/1000}秒')
    
def onWorkerEnd(changeNum):
    global __START_TIME__
    st = __START_TIME__
    forceGen = SettingUtil.getSetting().forceGen 
    if forceGen or changeNum > 0 or not os.path.exists(SettingUtil.getSetting().pbFile):
        writeConfigPB()
    else:
        print("数据没有变化无需重新导表..")
    et = timeit.default_timer()
    costMS = math.floor((et-st)*1000)
    print(f'~~导表完成~~~耗时{costMS/1000}秒')

def readFromPB():
    pbBytes = None
    with open(SettingUtil.getSetting().pbFile,"rb") as f:
        pbBytes = f.read()
        
    if pbBytes !=None:
        print("readFromPB pbBytes=",len(pbBytes))
        head = struct.unpack("<i",pbBytes[0:4])[0]
        mask = struct.unpack("<i",pbBytes[4:8])[0]
        N = struct.unpack("<i",pbBytes[8:12])[0]
        st = 12
        configDict = {}
        nameBit = 2
        lenBit = 4
        print("总配置数=",N)
        configModule = SettingUtil.getSetting().configModule
        for i in range(N):
            print(f"+++++++++++++++++++开始读取第{i+1}个表格+++++++++")
            nameSize = struct.unpack("<H",pbBytes[st:st+nameBit])[0]
            st += nameBit
            nameBytes = pbBytes[st:st+nameSize]
            name = nameBytes.decode()
            st += nameSize
            sheetSize = struct.unpack("<i",pbBytes[st:st+lenBit])[0]
            st += lenBit
            moduleName = PBGenUtil.sheetNameToModuleName(configModule)
            print("nameSize=",nameSize,"name=",name,"sheetSize=",sheetSize,"moduleName=",moduleName)
            configDict[name] = PBGenUtil.byteToMessageCollect(pbBytes[st:st+sheetSize],moduleName,name)
            st += sheetSize
        tail = struct.unpack("<i",pbBytes[st:st+4])[0]
        
        code = tail|mask
        print("head=",head,"mask=",mask,"tail=",tail,"code=",code,tail|mask,"head&mask",head&mask)
        if head == code:
            print("验证成功...")
        #print("configDict=",configDict)
        #print("-------------t_ItemConfig 总行数",len(configDict['t_ItemConfig']))



if __name__=="__main__":
    try:
        exportTable()
    except Exception as e:
        print('An exception occurred e=',e.with_traceback())
    #msg = input("完成可以关掉了")
    # readFromPB()
    

    
    

    

    
    
    
    
    



